﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;

namespace Ratatoskr.Toolbox
{
    public abstract class Cell : Entity
    {

        #region Fields & Properties

        public readonly int MaxDirection;

        protected Cell[] _adjacent;
        public Cell[] Adjacent { get { return _adjacent; } }

        protected readonly int _xLoc;
        protected readonly int _yLoc;
        public Vector2 Location
        {
            get { return new Vector2(_xLoc, _yLoc); }
        }

        public virtual Vector2 Aspect
        {
            get { return Vector2.One; }
        }

        public virtual float Constraint
        {
            get { return 1; }
        }

        public virtual int IntersectRange
        {
            get { return 1; }
        }

        #endregion

        #region Construction, Initialization & Placement

        public Cell(int xLoc, int yLoc, int faces = 1)
            : base("cell" + xLoc + "_" + yLoc)
        {
            if (faces < 1)
                throw new ArgumentOutOfRangeException("Cells must have at least 1 face.");
            MaxDirection = faces - 1;
            _xLoc = xLoc;
            _yLoc = yLoc;
        }

        protected override bool CannotDetach() { return true; }

        protected override void ExtendInitializePre()
        {
            if (_adjacent == null)
                _initialized = false;
        }

        protected internal void SetAdjacentCells(Cell[] cells)
        {
            if (cells.Count() != MaxDirection + 1)
                throw new ArgumentException("Invalid number of adjacent cells.");
            _adjacent = cells;
        }

        protected internal abstract void FindAdjacentCells();

        #endregion

        #region Inter-Cell Pathing

        public CellPath CellWalk(float x, float y)
        {
            CellPath path = new CellPath();
            _CellWalk(path, x, y);
            return path;
        }

        protected internal void _CellWalk(CellPath path, float x, float y)
        {
            path.NextCell = this;
            CellPosition pos = _CellStep(x, y);
            if (pos.cell == this)
                path.Position = pos;
            else pos.cell._CellWalk(path, pos.x, pos.y);
        }

        protected internal abstract CellPosition _CellStep(float x, float y);

        public int ClockwiseFrom(int direction)
        {
            if (direction < 0 || direction > MaxDirection)
                throw new ArgumentException("Invalid direction for this cell type.");
            if (direction == MaxDirection)
                return 0;
            return direction + 1;
        }

        public int CounterClockwiseFrom(int direction)
        {
            if (direction < 0 || direction > MaxDirection)
                throw new ArgumentException("Invalid direction for this cell type.");
            if (direction == 0)
                return MaxDirection;
            return direction - 1;
        }

        #endregion

        #region Neighbor Lists

        public IEnumerable<Cell> CellsInRange(int range)
        {
            if (range < 0)
                throw new ArgumentException("Cannot search for cells within a negative range.");
            yield return this;
            for (int i = 0; i <= MaxDirection; i++)
            {
                Cell a = _adjacent[i];
                if (range > 0)
                    if (a != null)
                        foreach (Cell b in a.TrunkScan(i, range - 1))
                            yield return b;
            }
        }

        protected IEnumerable<Cell> TrunkScan(int direction, int range)
        {
            yield return this;
            if (range > 0)
            {
                if (_adjacent[direction] != null)
                    foreach (Cell c in _adjacent[direction].TrunkScan(direction, range - 1))
                        yield return c;
                int branch = ClockwiseFrom(direction);
                if (_adjacent[branch] != null)
                    foreach (Cell c in _adjacent[branch].BranchScan(direction, range - 1))
                        yield return c;
            }
        }

        protected IEnumerable<Cell> BranchScan(int direction, int range)
        {
            yield return this;
            if (range > 0)
                if (_adjacent[direction] != null)
                    foreach (Cell c in _adjacent[direction].BranchScan(direction, range - 1))
                        yield return c;
        }

        #endregion

    }

    public class CellPath
    {
        protected List<Cell> _path = new List<Cell>();
        public Cell NextCell
        {
            set { _path.Add(value); }
        }
        public Cell[] Path
        {
            get { return _path.ToArray(); }
        }
        public CellPosition Position;
    }

    public struct CellPosition
    {
        public Cell cell;
        public float x;
        public float y;
        public bool outside;
        public CellPosition(Cell cell, float x, float y, bool outside = false)
        {
            this.cell = cell;
            this.x = x;
            this.y = y;
            this.outside = outside;
        }
    }
}